package asset;

public class DM {
    private static int[] dataMemory = new int[0xc00];
    private static boolean outputEnable = true;

    public static void outConfig(boolean ifoutput) {
        outputEnable = ifoutput;
    }

    /**
     * DM write
     * @param bytenum : write bytes num(4 for sw,2 for sh,1 for sb)
     */
    public static void write(int addr1, int data, byte bytenum) throws Exception {
        int addr = addr1 & 0xffffffff;
        if (bytenum != 1 && bytenum != 2 && bytenum != 4) {
            throw new Exception("data memory write module error!!!\n\t\twrite bytes num ="
                + bytenum);
        }
        if ((addr & (bytenum - 1)) != 0) {
            String errortype = bytenum == 4 ? "sw" : "sh";
            throw new Exception("data memory write address exception!!!\n\t\ttype:" +
                errortype + "\taddr:" + addr);
        }
        if (addr < 0 || 0x3000 < addr) {
            throw new Exception(
                String.format("data memory write address %08x range out of 0-0x3000!!!",addr));
        }
        
        if (bytenum == 4) {
            addr >>= 2;
            dataMemory[addr] = data & (0xffffffff);
        }
        else if (bytenum == 2) {
            int shift = (addr >>> 1) & 1;
            int select = shift == 1 ? 0x0000ffff : 0xffff0000;
            addr >>= 2;
            dataMemory[addr] = (dataMemory[addr] & select) | ((data & 0xffff) << (shift * 16));
        }
        else if (bytenum == 1) {
            int shift = addr & 0b11;
            addr >>= 2;
            int select = 0xffffffff ^ (0xff << (shift * 8));
            dataMemory[addr] = (dataMemory[addr] & select) | ((data & 0xff) << (shift * 8));
        }

        if (outputEnable) {
            System.out.print(
                String.format("@%08x: *%08x <= %08x",PC.getValue(),addr << 2,dataMemory[addr]));
        }
        
        PC.next();
    }

    public static void write(int addr1, int data) throws Exception {
        write(addr1, data, (byte)4);
    }

    /**
     * Memory Read
     * @param bytenum : read bytes num(4 for lw,2 for lh,1 for lb)
     * @param signed :true for signed load ; false for unsigned load
     */
    public static int read(int addr1,byte bytenum,boolean signed) throws Exception {
        int addr = addr1 & 0xffffffff;
        if (bytenum != 1 && bytenum != 2 && bytenum != 4) {
            throw new Exception("data memory read module error!!!\n\t\twrite bytes num =" +
                bytenum);
        }
        if ((addr & (bytenum - 1)) != 0) {
            String errortype = bytenum == 4 ? "sw" : "sh";
            throw new Exception("data memory read address exception!!!\n\t\ttype:" +
                errortype + "\taddr:" + addr);
        }
        if (addr < 0 || 0x3000 < addr) {
            throw new Exception("data memory read address " + addr + " range out of 0-0x3000!!!");
        }

        if (bytenum == 4) {
            addr >>= 2;
            return dataMemory[addr];
        }
        else if (bytenum == 2) {
            int shift = (addr >> 1) & 1;
            int select = shift == 1 ? 0xffff0000 : 0x0000ffff;
            addr >>= 2;
            int temp = (dataMemory[addr] & select) >> (shift * 16);
            return signed ? Define.sigExtNum(temp, 16) : temp;
        }
        else if (bytenum == 1) {
            int shift = addr & 0b11;
            int select = 0xff << (shift * 8);
            addr >>= 2;
            int temp = (dataMemory[addr] & select) >> (shift * 8);
            return signed ? Define.sigExtNum(temp, 8) : temp;
        }
        throw new Exception("bytenum " + bytenum + "is not 1 or 2 or 4");
    }

    /**
     * Memory Read
     * @param bytenum : read bytes num(4 for lw,2 for lh,1 for lb)
     * @param signed (= true) :true for signed load ; false for unsigned load
     */
    public static int read(int addr1,byte bytenum) throws Exception {
        return read(addr1, bytenum, true);
    }

    public static int read(int addr1) throws Exception {
        return read(addr1, (byte)4, true);
    }
}
